using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace CGShaderIntegration.DrawableGameComponents
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class KlomannMarcelShader : DrawableGameComponent, IMatrices
    {
        private Sonne sonne;
        private Erde erde;
        private Mond mond;
        private KeyboardState oldState;

        public Matrix WorldMatrix
        {
            get
            {
                return this.sonne.WorldMatrix;
            }
            set
            {
                this.sonne.WorldMatrix = value;
                this.erde.WorldMatrix = value;
                this.mond.WorldMatrix = value;
            }
        }

        public Matrix ViewMatrix
        {
            get
            {
                return this.sonne.ViewMatrix;
            }
            set
            {
                this.sonne.ViewMatrix = value;
                this.erde.ViewMatrix = value;
                this.mond.ViewMatrix = value;
            }
        }

        public Matrix ProjectionMatrix
        {
            get
            {
                return this.sonne.ProjectionMatrix;
            }
            set
            {
                this.sonne.ProjectionMatrix = value;
                this.erde.ProjectionMatrix = value;
                this.mond.ProjectionMatrix = value;
            }
        }

        public KlomannMarcelShader(Game game)
            : base(game)
        {
            game.Components.Add(this);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization logic here
            this.erde = new Erde(this.Game, new Vector3(6f, 0f, 0f));
            this.mond = new Mond(this.Game, new Vector3(6f, 0f, 0f));
            this.sonne = new Sonne(this.Game, mond, erde);
            this.sonne.Initialize();
            this.erde.Initialize();
            this.mond.Initialize();

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {

        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Enter) && !oldState.IsKeyDown(Keys.Enter))
            {
                this.erde.Invert();
                this.mond.Invert();
            }
            this.oldState = Keyboard.GetState();

            this.sonne.Update(gameTime);
            this.erde.Update(gameTime);
            this.mond.Update(gameTime);

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Draw(GameTime gameTime)
        {
            this.sonne.Draw(gameTime);
            this.erde.Draw(gameTime);
            this.mond.Draw(gameTime);

            base.Draw(gameTime);
        }

        public string GetName()
        {
            return "KlomannMarcel";
        }
    }

    public class Mond : Microsoft.Xna.Framework.DrawableGameComponent, IMatrices
    {
        private Model model;
        private ContentManager content;
        private Game game;
        private float rotationY;
        private Vector3 erdePosition, distanz;

#if(WINDOWS)
        private Effect schachbrett;
#endif
        private Matrix world = Matrix.Identity;

        private float sign;

        public Matrix WorldMatrixIntern
        {
            get { return this.world; }
        }

        public Model Model
        {
            get { return this.model; }
        }

        public Matrix WorldMatrix
        { get; set; }

        public Matrix ViewMatrix
        { get; set; }

        public Matrix ProjectionMatrix
        { get; set; }

        public Mond(Game game, Vector3 position)
            : base(game)
        {
            this.WorldMatrix = Matrix.Identity;
            this.game = game;
            this.rotationY = 0f;
            this.erdePosition = position;
            this.sign = 1f;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            this.content = this.game.Content;
            this.distanz = new Vector3(1.5f, 0f, 0f);
            base.Initialize();
        }

        protected override void LoadContent()
        {
            this.model = content.Load<Model>("models\\KlomannMarcelErde");
#if(WINDOWS)
            this.schachbrett = content.Load<Effect>("effects\\KlomannMarcelSchachbrett");

            foreach (ModelMesh mesh in this.model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = this.schachbrett;
                }
            }
#endif

            base.LoadContent();
        }

        public void Invert()
        {
            this.sign *= -1f;
        }


        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            this.rotationY += 0.2f * this.sign * (float)((Math.PI * gameTime.ElapsedGameTime.TotalSeconds / 5.0) % 2.0 * Math.PI);

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            this.world = Matrix.CreateScale(0.2f) * Matrix.CreateTranslation(this.distanz) * Matrix.CreateRotationY(this.rotationY) * Matrix.CreateTranslation(this.erdePosition) * Matrix.CreateRotationY(this.rotationY);
            Matrix worldLocal = this.world * this.WorldMatrix;
#if(WINDOWS_PHONE)

            foreach (ModelMesh mesh in this.model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    BasicEffect effect = part.Effect as BasicEffect;
                    if (effect != null)
                    {
                        effect.EnableDefaultLighting();
                        effect.EmissiveColor = Color.Gray.ToVector3();
                        effect.DirectionalLight0.Direction = Vector3.Forward;

                        effect.World = world;
                        effect.Projection = this.camera.Projection;
                        effect.View = this.camera.View;
                    }
                }
                mesh.Draw();
            }
#endif
#if(WINDOWS)
            foreach (var mesh in model.Meshes)
            {
                foreach (var meshPart in mesh.MeshParts)
                {
                    meshPart.Effect.Parameters["matViewProjection"].SetValue(this.ViewMatrix * this.ProjectionMatrix);
                    meshPart.Effect.Parameters["world"].SetValue(worldLocal);
                }
                mesh.Draw();
            }
#endif

            base.Draw(gameTime);
        }

        public string GetName()
        {
            return "KlomannMarcel";
        }
    }

    public class Sonne : Microsoft.Xna.Framework.DrawableGameComponent, IMatrices
    {
        private Model model;
        private ContentManager content;
        private Game game;
        private float rotationY = 0.0f;
        private float sign = 1.0f;

#if(WINDOWS)
        private RenderTarget2D renderTarget;
        private Effect renderToTexture;
        private Mond mond;
        private Erde erde;
        private Model[] models;
        private Matrix[] worldMatrices;
#endif

        public Matrix WorldMatrix
        { get; set; }

        public Matrix ViewMatrix
        { get; set; }

        public Matrix ProjectionMatrix
        { get; set; }


        public Sonne(Game game, Mond mond, Erde erde)
            : base(game)
        {
            this.WorldMatrix = Matrix.Identity;
            this.game = game;
#if(WINDOWS)
            this.mond = mond;
            this.erde = erde;
#endif
        }

        public void Invert()
        {
            this.sign *= -1.0f;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            this.content = this.game.Content;
            base.Initialize();
        }

        protected override void LoadContent()
        {
            this.model = content.Load<Model>("models\\KlomannMarcelErde");
#if(WINDOWS)
            this.renderToTexture = content.Load<Effect>("Effects\\KlomannMarcelRenderToTexture");
            this.renderTarget = new RenderTarget2D(this.game.GraphicsDevice, this.game.GraphicsDevice.Viewport.Width, this.game.GraphicsDevice.Viewport.Height);

            this.models = new Model[3];
            this.worldMatrices = new Matrix[3];
#endif

            base.LoadContent();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            this.rotationY += this.sign * (float)((Math.PI * gameTime.ElapsedGameTime.TotalSeconds / 5.0) % 2.0 * Math.PI);
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            Matrix worldIntern = Matrix.CreateScale(2f) * Matrix.CreateRotationY(MathHelper.ToRadians(180.0f));
            Matrix world = worldIntern * this.WorldMatrix;


#if(WINDOWS)
            this.worldMatrices[0] = worldIntern;
            this.worldMatrices[1] = erde.WorldMatrixIntern;
            this.worldMatrices[2] = mond.WorldMatrixIntern;
            this.models[0] = this.model;
            this.models[1] = erde.Model;
            this.models[2] = mond.Model;

            GraphicsDevice device = this.game.GraphicsDevice;

            device.SetRenderTarget(this.renderTarget);
            device.Clear(Color.Black);

            this.renderToTexture.Parameters["matProjection"].SetValue(this.ProjectionMatrix);
            this.renderToTexture.Parameters["LightPosition"].SetValue(new Vector3(0f, 5f, -15f));
            this.renderToTexture.Parameters["LightTarget"].SetValue(new Vector3(0f, -1f, -1f));
            this.renderToTexture.Parameters["fNearClipPlane"].SetValue(1f);
            this.renderToTexture.Parameters["fFarClipPlane"].SetValue(30f);
            this.renderToTexture.Parameters["lightView"].SetValue(Matrix.CreateLookAt(new Vector3(0, 5f, -15f),
                                                                                      new Vector3(0f, -1f, -1f),
                                                                                      Vector3.Up));

            for (int i = 0; i < worldMatrices.Length; i++)
            {
                this.renderToTexture.Parameters["world"].SetValue(worldMatrices[i]);
                foreach (var mesh in models[i].Meshes)
                {
                    foreach (var meshPart in mesh.MeshParts)
                    {
                        this.renderToTexture.CurrentTechnique.Passes[0].Apply();
                        device.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);
                        device.Indices = meshPart.IndexBuffer;
                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                                     0, 0,
                                                     meshPart.NumVertices,
                                                     meshPart.StartIndex,
                                                     meshPart.PrimitiveCount);
                    }
                }
            }

            device.SetRenderTarget(null);

            this.renderToTexture.Parameters["renderTexture_Tex"].SetValue(this.renderTarget);
            this.renderToTexture.Parameters["matWorldViewProjection"].SetValue(world * this.ViewMatrix * this.ProjectionMatrix);
            this.renderToTexture.Parameters["ambient"].SetValue(new Vector4(0.1f, 0.1f, 0.1f, 1.0f));
            foreach (var mesh in model.Meshes)
            {
                foreach (var meshPart in mesh.MeshParts)
                {
                    this.renderToTexture.CurrentTechnique.Passes[1].Apply();
                    device.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);
                    device.Indices = meshPart.IndexBuffer;
                    device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                                 0, 0,
                                                 meshPart.NumVertices,
                                                 meshPart.StartIndex,
                                                 meshPart.PrimitiveCount);
                }
            }

            //using (var stream = new System.IO.FileStream("d:\\test.png", System.IO.FileMode.Create))
            //{
            //    this.renderTarget.SaveAsPng(stream, 1024, 600);
            //}
#endif

            base.Draw(gameTime);
        }

        public string GetName()
        {
            return "KlomannMarcel";
        }
    }

    public class Erde : Microsoft.Xna.Framework.DrawableGameComponent, IMatrices
    {
        private ContentManager content;
        private Game game;
        private float rotationY;
        private Vector3 position;

        private Model model;
        private Matrix world = Matrix.Identity;

#if(WINDOWS)
        private Effect schachbrett;
#endif


        private float sign;

        public Matrix WorldMatrixIntern
        {
            get { return this.world; }
        }

        public Model Model
        {
            get { return this.model; }
        }

        public Matrix WorldMatrix
        { get; set; }

        public Matrix ViewMatrix
        { get; set; }

        public Matrix ProjectionMatrix
        { get; set; }

        public Erde(Game game, Vector3 position)
            : base(game)
        {
            this.WorldMatrix = Matrix.Identity;
            this.game = game;
            this.rotationY = 0f;
            this.position = position;
            this.sign = 1f;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            this.content = this.game.Content;
            base.Initialize();
        }

        protected override void LoadContent()
        {
            this.model = content.Load<Model>("models\\KlomannMarcelErde");

#if(WINDOWS)
            this.schachbrett = content.Load<Effect>("effects\\KlomannMarcelSchachbrett");

            foreach (ModelMesh mesh in this.model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = this.schachbrett;
                }
            }
#endif

            base.LoadContent();
        }

        public void Invert()
        {
            this.sign *= -1f;
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            this.rotationY += 0.2f * this.sign * (float)((Math.PI * gameTime.ElapsedGameTime.TotalSeconds / 5.0) % 2.0 * Math.PI);

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            world = Matrix.CreateScale(0.7f) * Matrix.CreateRotationY(this.rotationY) * Matrix.CreateRotationZ(MathHelper.ToRadians(23.5f)) * Matrix.CreateTranslation(this.position) * Matrix.CreateRotationY(this.rotationY);
            Matrix worldLocal = this.world * this.WorldMatrix;
#if(WINDOWS_PHONE)
            foreach (ModelMesh mesh in this.model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    BasicEffect effect = part.Effect as BasicEffect;
                    if (effect != null)
                    {
                        effect.EnableDefaultLighting();
                        effect.EmissiveColor = Color.Blue.ToVector3();
                        effect.DirectionalLight0.Direction = Vector3.Forward;

                        effect.World = world;
                        effect.Projection = this.camera.Projection;
                        effect.View = this.camera.View;
                    }
                }
                mesh.Draw();
            }
#endif
#if(WINDOWS)
            foreach (var mesh in model.Meshes)
            {
                foreach (var meshPart in mesh.MeshParts)
                {
                    meshPart.Effect.Parameters["matViewProjection"].SetValue(this.ViewMatrix * this.ProjectionMatrix);
                    meshPart.Effect.Parameters["world"].SetValue(worldLocal);
                }
                mesh.Draw();
            }
#endif
            base.Draw(gameTime);
        }


        public string GetName()
        {
            return "KlomannMarcel";
        }
    }
}
